package hotel.board;

import hotel.HotelConstants;
import hotel.board.circuit.Circuit;
import hotel.board.circuit.ICase;
import hotel.board.circuit.ICircuit;
import hotel.board.circuit.IModifiableCircuit;
import hotel.board.circuit.predefined.IPredefinedCircuit;
import hotel.dice.BuildDiceFaces;
import hotel.dice.Dice;
import hotel.dice.IDice;
import hotel.exception.BuildingsMissingException;
import hotel.exception.NotEnoughMoneyException;
import hotel.exception.NotEnoughPlaceException;
import hotel.exception.NotYourPropertyException;
import hotel.exception.UnbuyablePropertyException;
import hotel.exception.WaitingOperationException;
import hotel.hotel.HotelsArea;
import hotel.hotel.IHotelsArea;
import hotel.hotel.IModifiableHotelsArea;
import hotel.hotel.IProperty;
import hotel.player.IModifiablePlayer;
import hotel.player.IPlayer;
import hotel.player.Player;
import hotel.util.ObservableModel;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class Board extends ObservableModel implements IBoard, HotelConstants {
	
	// ATTRIBUTS
	
	private final List<IModifiablePlayer> players;
	protected IModifiablePlayer currentPlayer;
	private final IModifiableCircuit circuit;
	private final IModifiableHotelsArea hotels;
	private final IDice<Integer> moveDice;
	private final IDice<BuildDiceFaces> buildDice;
	protected final Map<IProperty, IPlayer> properties;
	protected boolean beginTurn;
	
	// CONSTRUCTEURS
	
	public Board(String[] names, IPredefinedCircuit c) {
		// Joueurs
		players = new ArrayList<IModifiablePlayer>();
		IModifiablePlayer p;
		for (String n : names) {
			p = new Player(n);
			p.setBalance(STARTING_MONEY);
			players.add(p);
		}
		currentPlayer = players.get((int) (Math.random() * players.size()));
		// Circuit
		circuit = new Circuit(players.toArray(new IPlayer[0]), c);
		// Hôtels
		hotels = new HotelsArea(c.getProperties());
		// Dés
		moveDice = new Dice<Integer>(new Integer[] {1, 2, 3, 4, 5, 6});
		buildDice = new Dice<BuildDiceFaces>(BuildDiceFaces.getCoeffValues());
		// Propriétés
		properties = new HashMap<IProperty, IPlayer>();
	}
	
	// REQUETES

	@Override
	public IPlayer[] getPlayers() {
		return players.toArray(new IPlayer[0]);
	}

	@Override
	public IPlayer getCurrentPlayer() {
		return currentPlayer;
	}

	@Override
	public ICircuit getCircuit() {
		return circuit;
	}

	@Override
	public IHotelsArea getHotelsArea() {
		return hotels;
	}
	
	@Override
	public Integer getMoveDiceFace() {
		return moveDice.getValue();
	}
	
	@Override
	public BuildDiceFaces getBuildDiceFace() {
		return buildDice.getValue();
	}
	
	@Override
	public IPlayer getOwner(IProperty p) {
		return properties.get(p);
	}
	
	// COMMANDES

	@Override
	public Integer rollMoveDice() {
		Integer old = moveDice.getValue();
		moveDice.roll();
		listeners.firePropertyChange(beginTurn ? "beginMoveDice" : "moveDice", old, moveDice.getValue());
		setBeginTurn(false);
		return moveDice.getValue();
	}

	@Override
	public BuildDiceFaces rollBuildDice() {
		BuildDiceFaces old = buildDice.getValue();
		buildDice.roll();
		listeners.firePropertyChange("buildDice", old, buildDice.getValue());
		return buildDice.getValue();
	}

	@Override
	public void walkPlayer() {
		ICase oldCase = circuit.getCase(currentPlayer);
		circuit.advance(currentPlayer, moveDice.getValue());
		listeners.firePropertyChange("position", oldCase, circuit.getCase(currentPlayer));
	}
	
	@Override
	public void buildEntry(ICase c, int side) throws NotEnoughPlaceException, BuildingsMissingException, NotYourPropertyException, NotEnoughMoneyException {
		IProperty prop = c.getProperty(side);
		if (properties.get(prop) != currentPlayer) {
			throw new NotYourPropertyException();
		}
		circuit.buildEntry(c, side);
	}

	@Override
	public void build(IProperty p) throws NotYourPropertyException, NotEnoughPlaceException, NotEnoughMoneyException {
		if (properties.get(p) != currentPlayer) {
			throw new NotYourPropertyException();
		}
		hotels.build(p);
	}

	@Override
	public void addFunArea(IProperty p) throws NotEnoughMoneyException, NotYourPropertyException, BuildingsMissingException, NotEnoughPlaceException {
		if (properties.get(p) != currentPlayer) {
			throw new NotYourPropertyException();
		}
		if (currentPlayer.getBalance() < p.getFunAreaCost()) {
			throw new NotEnoughMoneyException();
		}
		hotels.addFunArea(p);
		currentPlayer.subMoney(p.getFunAreaCost());
	}

	@Override
	public void buy(IProperty p) throws NotEnoughMoneyException, UnbuyablePropertyException {
		ICase c = circuit.getCase(currentPlayer);
		if (c.getProperty(ICase.RIGHT_SIDE) != p && c.getProperty(ICase.LEFT_SIDE) != p) {
			throw new IllegalArgumentException();
		}
		IPlayer owner = properties.get(p);
		if (owner == null) {
			if (currentPlayer.getBalance() < p.getCost()) {
				throw new NotEnoughMoneyException();
			}
			currentPlayer.subMoney(p.getCost());
		} else {
			if (owner == currentPlayer || hotels.getBuiltNb(p) > 0) {
				throw new UnbuyablePropertyException();
			}
			if (currentPlayer.getBalance() < p.getReCost()) {
				throw new NotEnoughMoneyException();
			}
			currentPlayer.subMoney(p.getReCost());
		}
		properties.put(p, currentPlayer);
		listeners.firePropertyChange("owner", p, currentPlayer);
	}

	@Override
	public void sellProperty(IProperty p, long cost, IPlayer to) throws NotYourPropertyException, NotEnoughMoneyException {
		if (properties.get(p) != currentPlayer) {
			throw new NotYourPropertyException();
		}
		if (to.getBalance() < cost) {
			throw new NotEnoughMoneyException();
		}
		players.get(players.indexOf(to)).subMoney(cost);
		properties.put(p, to);
		currentPlayer.addMoney(cost);
	}

	@Override
	public void endTurn() throws WaitingOperationException {
		IPlayer old = currentPlayer;
		currentPlayer = players.get((players.indexOf(currentPlayer) + 1) % players.size());
		listeners.firePropertyChange("current", old, currentPlayer);
	}
	
	// OUTILS
	
	protected void setBeginTurn(boolean is) {
		boolean old = beginTurn;
		beginTurn = is;
		listeners.firePropertyChange("beginTurn", old, is);
	}
	
	/*private IProperty getCurrentRentable() {
		ICase c = circuit.getCase(currentPlayer);
		if (c.hasEntry(ICase.RIGHT_SIDE) && properties.get(c.getProperty(ICase.RIGHT_SIDE)) != currentPlayer) {
			return c.getProperty(ICase.RIGHT_SIDE);
		}
		if (c.hasEntry(ICase.LEFT_SIDE) && properties.get(c.getProperty(ICase.LEFT_SIDE)) != currentPlayer) {
			return c.getProperty(ICase.LEFT_SIDE);
		}
		return null;
	}
	
	private boolean canBuyFunArea() {
		for (IProperty p : properties.keySet()) {
			if (hotels.getBuiltNb(p) == p.getCapacity()
					&& currentPlayer.getBalance() >= p.getFunAreaCost()) {
				return true;
			}
		}
		return false;
	}*/

}
